#region

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

#endregion

namespace AjaxControlToolkit.LongOperations
{
    public delegate void WorkRequestDelegate(object state, DateTime requestEnqueueTime);

    public delegate void ThreadPoolDelegate();

    #region ThreadPool class

    public sealed class ThreadPool : WaitHandle
    {
        #region ThreadPool constructors

        public ThreadPool(int initialThreadCount, int maxThreadCount, string poolName)
                : this(initialThreadCount, maxThreadCount, poolName,
                       DEFAULT_NEW_THREAD_TRIGGER_TIME,
                       DEFAULT_DYNAMIC_THREAD_DECAY_TIME,
                       DEFAULT_THREAD_PRIORITY,
                       DEFAULT_REQUEST_QUEUE_LIMIT)
        {
        }

        public ThreadPool(int initialThreadCount, int maxThreadCount, string poolName,
                          int newThreadTrigger, int dynamicThreadDecayTime,
                          ThreadPriority threadPriority, int requestQueueLimit)
        {
            Debug.WriteLine(string.Format("New thread pool {0} created:", poolName));
            Debug.WriteLine(string.Format("  initial thread count:      {0}", initialThreadCount));
            Debug.WriteLine(string.Format("  max thread count:          {0}", maxThreadCount));
            Debug.WriteLine(string.Format("  new thread trigger:        {0} ms", newThreadTrigger));
            Debug.WriteLine(string.Format("  dynamic thread decay time: {0} ms", dynamicThreadDecayTime));
            Debug.WriteLine(string.Format("  request queue limit:       {0} entries", requestQueueLimit));

            Handle = this.stopCompleteEvent.Handle;

            if (maxThreadCount < initialThreadCount)
            {
                throw new ArgumentException("Maximum thread count must be >= initial thread count.", "maxThreadCount");
            }

            if (dynamicThreadDecayTime <= 0)
            {
                throw new ArgumentException("Dynamic thread decay time cannot be <= 0.", "dynamicThreadDecayTime");
            }

            if (newThreadTrigger <= 0)
            {
                throw new ArgumentException("New thread trigger time cannot be <= 0.", "newThreadTrigger");
            }

            this.initialThreadCount = initialThreadCount;
            this.maxThreadCount = maxThreadCount;
            this.requestQueueLimit = (requestQueueLimit < 0 ? DEFAULT_REQUEST_QUEUE_LIMIT : requestQueueLimit);
            this.decayTime = dynamicThreadDecayTime;
            this.newThreadTrigger = new TimeSpan(TimeSpan.TicksPerMillisecond*newThreadTrigger);
            this.threadPriority = threadPriority;
            this.requestQueue = new Queue(requestQueueLimit < 0 ? 4096 : requestQueueLimit);

            if (poolName == null)
            {
                throw new ArgumentNullException("poolName", "Thread pool name cannot be null");
            }
            else
            {
                this.threadPoolName = poolName;
            }
        }

        #endregion

        #region ThreadPool properties

        // The Priority & DynamicThreadDecay properties are not thread safe
        // and can only be set before Start is called.
        //
        public ThreadPriority Priority
        {
            get
            {
                return (this.threadPriority);
            }

            set
            {
                if (this.hasBeenStarted)
                {
                    throw new InvalidOperationException("Cannot adjust thread priority after pool has been started.");
                }

                this.threadPriority = value;
            }
        }

        public int DynamicThreadDecay
        {
            get
            {
                return (this.decayTime);
            }

            set
            {
                if (this.hasBeenStarted)
                {
                    throw new InvalidOperationException("Cannot adjust dynamic thread decay time after pool has been started.");
                }

                if (value <= 0)
                {
                    throw new ArgumentException("Dynamic thread decay time cannot be <= 0.", "value");
                }

                this.decayTime = value;
            }
        }

        public int NewThreadTrigger
        {
            get
            {
                return ((int)this.newThreadTrigger.TotalMilliseconds);
            }

            set
            {
                if (value <= 0)
                {
                    throw new ArgumentException("New thread trigger time cannot be <= 0.", "value");
                }

                lock (this)
                {
                    this.newThreadTrigger = new TimeSpan(TimeSpan.TicksPerMillisecond*value);
                }
            }
        }

        public int RequestQueueLimit
        {
            get
            {
                return (this.requestQueueLimit);
            }
            set
            {
                this.requestQueueLimit = (value < 0 ? DEFAULT_REQUEST_QUEUE_LIMIT : value);
            }
        }

        public int AvailableThreads
        {
            get
            {
                return (this.maxThreadCount - this.currentThreadCount);
            }
        }

        public int MaxThreads
        {
            get
            {
                return (this.maxThreadCount);
            }

            set
            {
                if (value < this.initialThreadCount)
                {
                    throw new ArgumentException("Maximum thread count must be >= initial thread count.", "MaxThreads");
                }

                this.maxThreadCount = value;
            }
        }

        public bool IsStarted
        {
            get
            {
                return (this.hasBeenStarted);
            }
        }

        public bool PropogateThreadPrincipal
        {
            get
            {
                return (this.propogateThreadPrincipal);
            }
            set
            {
                this.propogateThreadPrincipal = value;
            }
        }

        public bool PropogateCallContext
        {
            get
            {
                return (this.propogateCallContext);
            }
            set
            {
                this.propogateCallContext = value;
            }
        }

        public bool PropogateHttpContext
        {
            get
            {
                return (this.propogateHttpContext);
            }
            set
            {
                this.propogateHttpContext = value;
            }
        }

        public bool PropogateCASMarkers
        {
            get
            {
                return (this.propogateCASMarkers);
            }
            // When CompressedStack get/set is opened up,
            // add the following setter back in.
            //
            // set { propogateCASMarkers = value; }
        }

        public bool IsBackground
        {
            get
            {
                return (this.useBackgroundThreads);
            }

            set
            {
                if (this.hasBeenStarted)
                {
                    throw new InvalidOperationException("Cannot adjust background status after pool has been started.");
                }

                this.useBackgroundThreads = value;
            }
        }

        #endregion

        #region ThreadPool events

        public event ThreadPoolDelegate Started;
        public event ThreadPoolDelegate Stopped;

        #endregion

        public void Start()
        {
            lock (this)
            {
                if (this.hasBeenStarted)
                {
                    throw new InvalidOperationException("Pool has already been started.");
                }

                this.hasBeenStarted = true;

                // Check to see if there were already items posted to the queue
                // before Start was called.  If so, reset their timestamps to
                // the current time.
                //
                if (this.requestQueue.Count > 0)
                {
                    ResetWorkRequestTimes();
                }

                threads = new List<ThreadWrapper>(this.initialThreadCount);
                for (int n = 0; n < this.initialThreadCount; n++)
                {
                    ThreadWrapper thread =
                            new ThreadWrapper(this, true, this.threadPriority,
                                              string.Format("{0} (static)", this.threadPoolName));
                    threads.Add(thread);
                    thread.Start();
                }

                if (Started != null)
                {
                    Started(); // TODO: reconsider firing this event while holding the lock...
                }
            }
        }

        // The actual implementation of PostRequest.
        //
        internal bool PostRequest(WorkRequest request)
        {
            lock (this)
            {
                // A requestQueueLimit of -1 means the queue is "unbounded"
                // (subject to available resources).  IOW, no artificial limit
                // has been placed on the maximum # of requests that can be
                // placed into the queue.
                //
                if ((this.requestQueueLimit == -1) || (this.requestQueue.Count < this.requestQueueLimit))
                {
                    try
                    {
                        this.requestQueue.Enqueue(request);
                        Monitor.Pulse(this);
                        return (true);
                    }
                    catch
                    {
                    }
                }
            }

            return (false);
        }

        internal void ResetWorkRequestTimes()
        {
            lock (this)
            {
                DateTime newTime = DateTime.Now; // DateTime.Now.Add(pool.newThreadTrigger);

                foreach (WorkRequest wr in this.requestQueue)
                {
                    wr.workingTime = newTime;
                }
            }
        }

        #region Private ThreadPool constants

        // Default parameters.
        //
        private const int DEFAULT_DYNAMIC_THREAD_DECAY_TIME = 5 /* minutes */*60 /* sec/min */*1000 /* ms/sec */;
        private const int DEFAULT_NEW_THREAD_TRIGGER_TIME = 500; // milliseconds
        private const ThreadPriority DEFAULT_THREAD_PRIORITY = ThreadPriority.Normal;
        private const int DEFAULT_REQUEST_QUEUE_LIMIT = -1; // unbounded

        #endregion

        #region Private ThreadPool member variables

        private readonly int initialThreadCount; // Initial # of threads to create (called "static threads" in this class).
        internal List<ThreadWrapper> threads;
        internal readonly string threadPoolName;
        internal int currentThreadCount; // Current # of threads in the pool (static + dynamic).
        internal int decayTime; // If a dynamic thread is idle for this period of time w/o processing work requests, it will exit.
        internal bool hasBeenStarted;
        internal int maxThreadCount; // Cap for thread count.  Threads added above initialThreadCount are called "dynamic" threads.

        internal TimeSpan newThreadTrigger;
                          // If a work request sits in the queue this long before being processed, a new thread will be added to queue up to the max.

        private bool propogateCASMarkers;
        private bool propogateCallContext;
        private bool propogateHttpContext;
        private bool propogateThreadPrincipal;

        internal Queue requestQueue;
        private int requestQueueLimit; // Throttle for maximum # of work requests that can be added.
        internal ManualResetEvent stopCompleteEvent = new ManualResetEvent(false); // Signaled after Stop called and last thread exits.
        internal bool stopInProgress;
        private ThreadPriority threadPriority;
        internal bool useBackgroundThreads = true;

        #endregion

        #region ThreadPool.Stop and InternalStop

        public void FireStop()
        {
            if (Stopped != null)
            {
                Stopped();
            }
        }

        public bool AbortWorkRequest(Guid workRequestID)
        {
            bool wasFound = false;
            if (!this.hasBeenStarted)
                return wasFound;
            lock (this)
            {
                if (stopInProgress)
                    return wasFound;
                //Deleting requests thet are not started
                List<WorkRequest> requests = new List<WorkRequest>();
                WorkRequest wr = null;
                do
                {
                    wr = requestQueue.Dequeue() as WorkRequest;
                    if (wr != null)
                    {
                        if (wr.WorkRequestID == workRequestID)
                        {
                            wr = null;
                            wasFound = true;
                        }
                        else
                        {
                            requests.Add(wr);
                        }
                    }
                } while(wr != null);
                for (int i = requests.Count-1; i > -1 ; i--)
                {
                    requestQueue.Enqueue(requests[i]);
                    Monitor.Pulse(this);
                }
                requests.Clear();
                if (!wasFound)
                {
                    for (int i = 0; i < threads.Count; i++)
                    {
                        if (threads[i].CurrentWorkRequestGuid == workRequestID)
                        {
                            ThreadWrapper thread = threads[i];
                            threads.RemoveAt(i);
                            try
                            {
                                thread.Abort();
                            }
                            catch(Exception e)
                            {
                            }
                            wasFound = true;
                            break;
                        }
                    }
                }
            }
            return wasFound;
        }

        public void Stop()
        {
            InternalStop(false, Timeout.Infinite);
        }

        public void StopAndWait()
        {
            InternalStop(true, Timeout.Infinite);
        }

        public bool StopAndWait(int timeout)
        {
            return InternalStop(true, timeout);
        }

        private bool InternalStop(bool wait, int timeout)
        {
            if (!this.hasBeenStarted)
            {
                throw new InvalidOperationException("Cannot stop a thread pool that has not been started yet.");
            }

            lock (this)
            {
                Debug.WriteLine(string.Format("[{0}, {1}] Stopping pool (# threads = {2})",
                                              AppDomain.GetCurrentThreadId(), Thread.CurrentThread.Name,
                                              this.currentThreadCount));
                this.stopInProgress = true;
                Monitor.PulseAll(this);
            }

            if (wait)
            {
                bool stopComplete = WaitOne(timeout, true);

                if (stopComplete)
                {
                    // If the stop was successful, we can support being
                    // to be restarted.  If the stop was requested, but not
                    // waited on, then we don't support restarting.
                    //
                    this.hasBeenStarted = false;
                    this.stopInProgress = false;
                    this.requestQueue.Clear();
                    this.stopCompleteEvent.Reset();
                }

                return (stopComplete);
            }

            return (true);
        }

        #endregion

        #region ThreadPool.PostRequest(early bound)

        // Overloads for the early bound WorkRequestDelegate-based targets.
        //
        public bool PostRequest(WorkRequestDelegate cb)
        {
            return PostRequest(cb, (object)null);
        }

        public bool PostRequest(WorkRequestDelegate cb, object state)
        {
            IWorkRequest notUsed;
            return PostRequest(cb, state, out notUsed);
        }

        public bool PostRequest(WorkRequestDelegate cb, object state, out IWorkRequest reqStatus)
        {
            WorkRequest request =
                    new WorkRequest(cb, state,
                                    this.propogateThreadPrincipal, this.propogateCallContext,
                                    this.propogateHttpContext, this.propogateCASMarkers);
            reqStatus = request;
            return PostRequest(request);
        }

        #endregion

        #region ThreadPool.PostRequest(late bound)

        // Overloads for the late bound Delegate.DynamicInvoke-based targets.
        //
        public bool PostRequest(Delegate cb, object[] args)
        {
            IWorkRequest notUsed;
            return PostRequest(cb, args, out notUsed);
        }

        public bool PostRequest(Delegate cb, object[] args, out IWorkRequest reqStatus)
        {
            WorkRequest request =
                    new WorkRequest(cb, args,
                                    this.propogateThreadPrincipal, this.propogateCallContext,
                                    this.propogateHttpContext, this.propogateCASMarkers);
            reqStatus = request;
            return PostRequest(request);
        }

        #endregion
    }

    #endregion
}